﻿/*
 *  written and tested by qanplazas
 */
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Matrix object for it's calculations
/// </summary>

namespace MadLibrary.Math
{

    public class Matrix
    {

        #region fields
        private ArrayList MatrixValues;

        /// <summary>
        /// matrix row count
        /// </summary>
        private int Matrix_Row_Count;

        /// <summary>
        /// matrix column count
        /// </summary>
        private int Matrix_Column_Count;

        public int RowCount
        {
            get { return ((ArrayList)(MatrixValues[0])).Count; }
        }

        public int ColumnCount
        {
            get { return MatrixValues.Count; }
        }

        #endregion

        #region constructors
        /// <summary>
        /// Create empty matrix
        /// </summary>
        public Matrix()
        {
            MatrixValues = new ArrayList();
            Matrix_Row_Count = 0;
            Matrix_Column_Count = 0;
        }

        public Matrix(ArrayList list)
        {
            MatrixValues = list;
            Matrix_Column_Count = list.Count;
            Matrix_Row_Count = ((ArrayList)list[0]).Count;

        }

        /// <summary>
        /// Create n x m matrix 
        /// </summary>
        /// <param name="n">matrix row count</param>
        /// <param name="m">matrix column count</param>
        public Matrix(int n, int m)
        {
            Matrix_Row_Count = n;
            Matrix_Column_Count = m;
            MatrixValues = new ArrayList(n);

            for (int i = 0; i < n; i++)
            {
                MatrixValues.Add(new ArrayList(m));

                for (int j = 0; j < m; j++)
                {
                    ((ArrayList)MatrixValues[i]).Add(0);
                }
            }
        }

        /// <summary>
        /// Create square matrix
        /// </summary>
        /// <param name="n">the size of square matrix</param>
        public Matrix(int n)
        {
            Matrix_Row_Count = n;
            Matrix_Column_Count = n;

            for (int i = 0; i < n; i++)
            {
                MatrixValues.Add(new ArrayList(n));

                for (int j = 0; j < n; j++)
                {
                    ((ArrayList)MatrixValues[i]).Add(0);
                }
            }
        }

        /// <summary>
        /// Create identity matrix with specified value
        /// </summary>
        /// <param name="identity">the value of identity</param>
        /// <param name="n">size of the matrix</param>
        public Matrix(double identity, int n)
        {
            Matrix_Column_Count = n;
            Matrix_Row_Count = n;

            for (int i = 0; i < n; i++)
            {
                MatrixValues.Add(new ArrayList(n));

                for (int j = 0; j < n; j++)
                {
                    if (i == j)
                    {
                        ((ArrayList)MatrixValues[i]).Add(identity);
                    }
                    else
                    {
                        ((ArrayList)MatrixValues[i]).Add(0);
                    }
                }
            }
        }

        /// <summary>
        /// create matrix from 2D array. if array is null, empty matrix will be created.
        /// </summary>
        /// <param name="_2DArray">array to make a matrix</param>
        public Matrix(double[,] _2DArray)
        {
            if (_2DArray == null)
            {
                //create 0x0 matrix.
                MatrixValues = new ArrayList();
                Matrix_Row_Count = 0;
                Matrix_Column_Count = 0;
            }
            Matrix_Row_Count = _2DArray.GetLength(0);
            Matrix_Column_Count = _2DArray.GetLength(1);


            MatrixValues = new ArrayList(Matrix_Row_Count);

            for (int i = 0; i < Matrix_Row_Count; i++)
            {
                MatrixValues.Add(new ArrayList(Matrix_Column_Count));

            }

            for (int i = 0; i < Matrix_Row_Count; i++)
            {
                for (int j = 0; j < Matrix_Column_Count; j++)
                {
                    ((ArrayList)(MatrixValues[i])).Add(_2DArray[i, j]);
                }
            }
        }

        #endregion

        #region operators
        public static Matrix operator +(Matrix A, Matrix B)
        {
            if (A.Matrix_Column_Count == B.Matrix_Column_Count &&
                A.Matrix_Row_Count == B.Matrix_Row_Count)
            {
                for (int i = 0; i < A.Matrix_Column_Count; i++)
                {
                    for (int j = 0; j < A.Matrix_Row_Count; j++)
                    {
                        A[i, j] += B[i, j];
                    }
                }
                return A;
            }
            else
            {
                throw new ArgumentException("Matrix dimmensions must match.");
            }

        }


        public static Matrix operator -(Matrix A, Matrix B)
        {
            if (A.Matrix_Column_Count == B.Matrix_Column_Count &&
                A.Matrix_Row_Count == B.Matrix_Row_Count)
            {
                for (int i = 0; i < A.Matrix_Column_Count; i++)
                {
                    for (int j = 0; j < A.Matrix_Row_Count; j++)
                    {
                        A[i, j] -= B[i, j];
                    }
                }
                return A;
            }
            else
            {
                throw new ArgumentException("Matrix dimmensions must match.");
            }

        }

        public static Matrix operator *(Matrix A, double k)
        {
            Matrix res = new Matrix(A.Matrix_Row_Count, A.Matrix_Column_Count);
            for (int i = 0; i < A.Matrix_Column_Count; i++)
            {
                for (int j = 0; j < A.Matrix_Row_Count; j++)
                {
                    res[i, j] = A[i, j] * k;
                }
            }

            return res;
        }
        public static Matrix operator *(Matrix A, Matrix B)
        {

            if (A.Matrix_Column_Count != B.Matrix_Row_Count)
            {
                throw new ArithmeticException("Matrix A column count must match matrix B row count.");
            }
            else if (A == null || B == null)
            {
                throw new NullReferenceException("Matrix can not be null.");
            }
            else
            {
                Matrix res = new Matrix(A.Matrix_Row_Count, B.Matrix_Column_Count);
                for (int i = 0; i < A.Matrix_Row_Count; i++)
                {
                    for (int j = 0; j < B.Matrix_Column_Count; j++)
                    {
                        res[i, j] = GetSum((ArrayList)A[i, false], (ArrayList)B[j, true]);
                    }
                }
                return res;
            }
        }
        #endregion

        #region virtuals
        /// <summary>
        /// Accsess matrix element and / or change it.
        /// </summary>
        /// <param name="index_x">x-index of the matrix. zero based index.</param>
        /// <param name="index_y">y-index of the matrix. zero based index.</param>
        /// <returns>value at the specified location</returns>
        public virtual double this[int index_x, int index_y]
        {
            get
            {
                if (index_x <= ColumnCount && index_y <= RowCount &&
                    index_x >= 0 && index_y >= 0)
                {
                    return (double)(((ArrayList)MatrixValues[index_x])[index_y]);
                }
                else
                {
                    throw new ArgumentOutOfRangeException("i");
                }
            }

            set
            {
                if (index_x < ColumnCount && index_y < RowCount &&
                   index_x >= 0 && index_y >= 0)
                {
                    (((ArrayList)MatrixValues[index_x])[index_y]) = value;
                }
                else
                {
                    throw new ArgumentOutOfRangeException("index");
                }
            }
        }
        /// <summary>
        /// Get row of the matrix at the specified index.
        /// </summary>
        /// <param name="index">index of the row</param>
        /// <param name="select_column">true if you want to select column, false - row</param>
        /// <returns>ArrayList of values in column of the specified index</returns>
        public virtual ArrayList this[int index, bool select_column]
        {
            get
            {
                ArrayList result = new ArrayList();
                if (index >= 0)
                {
                    switch (select_column)
                    {
                        case false: //select row
                            if (index < Matrix_Row_Count)
                            {
                                for (int j = 0; j < Matrix_Column_Count; j++)
                                {
                                    result.Add(((ArrayList)MatrixValues[index])[j]);    //i - row index, j - column index
                                }
                            }
                            else
                                throw new ArgumentOutOfRangeException("i");
                            return result;
                        default:    //select column
                            if (index < Matrix_Column_Count)
                            {
                                for (int j = 0; j < Matrix_Row_Count; j++)
                                {
                                    result.Add(((ArrayList)MatrixValues[j])[index]);    //j - row index, i  column index
                                }
                            }
                            return result;
                    }
                }
                else
                    throw new ArgumentException("Argument must be greater than or equal to zero.");
            }
        }

        #endregion

        #region private funcs
        /// <summary>
        /// Used for matrix * matrix multiplication.
        /// </summary>
        /// <param name="row">Row of matrix A.</param>
        /// <param name="column">column of matrix B.</param>
        /// <returns></returns>
        private static double GetSum(ArrayList row, ArrayList column)
        {
            int row_c = 0;
            int col_c = 0;

            double mult = 0;
            double sum = 0;

            row_c = row.Count;
            col_c = column.Count;

            for (int i = 0; i < row_c; i++)
            {
                mult = (double)row[i] * (double)column[i];
                sum += mult;
            }
            return sum;
        }

        /// <summary>
        /// Get 2x2 matrix determinant.
        /// </summary>
        /// <param name="m">2x2 matrix.</param>
        /// <returns>Determinant value</returns>
        private static double GetTempDet(Matrix m)
        {
            return m[0, 0] * m[1, 1] - m[0, 1] * m[1, 0];
        }

        /// <summary>
        /// Minor of the matrix, at the specified location.
        /// </summary>
        /// <param name="matrixElement"></param>
        /// <param name="matrixElementLocation"></param>
        /// <returns></returns>
        private static double GetMinor(double matrixElement, IntCoordinates matrixElementLocation)
        {
            if ((matrixElementLocation.column_coordinate + matrixElementLocation.row_coordinate) % 2 > 0)
            {
                return matrixElement * -1;
            }
            else
            {
                return matrixElement;
            }
        }
        #endregion

        #region public funcs
        /// <summary>
        /// Get lower generation matrix. It doesn't calculate minor. It is used for getting determinant.
        /// </summary>
        /// <param name="m">The matrix to substract from.</param>
        /// <param name="adjunctCoordinates">Shows which column and row has to be ignored. </param>
        /// <returns>The lower generation matrix</returns>
        private static Matrix GetAdjunct(Matrix m, IntCoordinates adjunctCoordinates)
        {
            Matrix res = new Matrix(m.ColumnCount - 1, m.RowCount - 1);
            ArrayList values = new ArrayList();
            for (int i = 0; i < m.RowCount; i++)
            {
                for (int j = 0; j < m.ColumnCount; j++)
                {
                    if (i != adjunctCoordinates.column_coordinate && j != adjunctCoordinates.row_coordinate)    //if element can be adjunct element
                    {
                        values.Add(m[i, j]);    //add to temporary arraylist
                    }
                }
            }

            //writing from temp listarray to result matrix
            int k = 0;
            for (int i = 0; i < res.RowCount; i++)
            {
                for (int j = 0; j < res.ColumnCount; j++)
                {
                    res[i, j] = (double)values[k];
                    k++;
                }
            }



            return res;
        }

        public static double GetDeterminant(Matrix m)
        {
            double sum = 0;
            if (m.ColumnCount == 2 &&
                m.RowCount == 2)
            {
                return GetTempDet(m);
            }

            else if (m.ColumnCount > 2 &&
                m.RowCount == m.ColumnCount)
            {
                for (int i = 0; i < m.ColumnCount; i++)
                {
                    sum += GetDeterminant(GetAdjunct(m, new IntCoordinates(i, 0))) * GetMinor(m[i, 0], new IntCoordinates(i, 0));
                }

                return sum;
            }

            else if (m.RowCount == 1 &&
                m.ColumnCount == m.RowCount)
            {
                return m[0, 0];
            }
            else
            {
                throw new ArithmeticException("The given matrix is not a square.");
            }
        }
        #region overrides
        public override string ToString()
        {
            string res = string.Empty;
            for (int i = 0; i < Matrix_Row_Count; i++)
            {
                foreach (var element in ((ArrayList)MatrixValues[i]))
                {
                    res = res + element + " ";
                }
                res += "\n";
            }
            return res;
        }
        #endregion
        #endregion
    }

    #region structs
    /// <summary>
    /// structure for creating submatrix. Only int allowed
    /// </summary>
    public struct IntCoordinates
    {
        /// <summary>
        /// column index of matrix
        /// </summary>
        public int column_coordinate;
        /// <summary>
        /// row index of matrix
        /// </summary>
        public int row_coordinate;

        public IntCoordinates(int x, int y)
        {
            column_coordinate = x;
            row_coordinate = y;
        }
    }
    #endregion

}
